﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Device.Location;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls.Maps;

namespace Tequila.Phone.Controls.Maps
{
    /// <summary>
    /// Map with marker oriented for MVVM usage.
    /// </summary>
    [TemplatePart(Name = "Map", Type = typeof(Map))]
    public class PinnedMap : ContentControl
    {
        private bool _skipCenterChangedNotification;

        /// <summary>
        /// Used to notify when user tap any point on the map
        /// </summary>
        public event EventHandler<NewLocationChosenEventArgs> NewLocationChosen;

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom",
                                        typeof(double),
                                        typeof(PinnedMap),
                                        new PropertyMetadata(12.0));

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static DependencyProperty ApplicationIdProperty =
            DependencyProperty.Register("ApplicationId",
                                        typeof(string),
                                        typeof(PinnedMap),
                                        null);

        /// <summary>
        /// Center dependency property
        /// </summary>
        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center",
                                        typeof(GeoCoordinate),
                                        typeof(PinnedMap),
                                        new PropertyMetadata(null, OnCenterPropertyChanged));

        /// <summary>
        /// Pins dependency property
        /// </summary>
        public static readonly DependencyProperty PinsProperty =
                                        DependencyProperty.Register("Pins",
                                        typeof(ObservableCollection<Pin>),
                                        typeof(PinnedMap),

                                        new PropertyMetadata(OnPinsPropertyChanged));

        /// <summary>
        /// PushPinStyle dependency property
        /// </summary>
        public static readonly DependencyProperty PushPinStyleProperty =
            DependencyProperty.Register("PushPinStyle",
                                        typeof(Style),
                                        typeof(PinnedMap), null);

        /// <summary>
        /// PushPinStyle property
        /// </summary>
        public Style PushPinStyle
        {
            get
            {
                return (Style)GetValue(PushPinStyleProperty);
            }
            set
            {
                SetValue(PushPinStyleProperty, value);
            }
        }

        /// <summary>
        /// Pins property
        /// </summary>
        public ObservableCollection<Pin> Pins
        {
            get
            {
                return (ObservableCollection<Pin>)GetValue(PinsProperty);
            }

            set
            {
                SetValue(PinsProperty, value);
            }
        }

        /// <summary>
        /// Used to set initial location, map will be shown with focus ob this point
        /// </summary>
        public GeoCoordinate Center
        {
            get
            {
                return (GeoCoordinate)GetValue(CenterProperty);
            }

            set
            {
                SetValue(CenterProperty, value);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Tequila.Phone.Controls.Maps.PinnedMap" /> class.
        /// </summary>
        public PinnedMap()
        {
            DefaultStyleKey = typeof(PinnedMap);
            Tap += OnMapTap;
        }

        /// <summary>
        /// Gets or sets Zoom value.
        /// </summary>
        public double Zoom
        {
            get { return (double)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, value); }
        }

        /// <summary>
        /// Gets or sets Application ID.
        /// </summary>
        public string ApplicationId
        {
            get { return GetValue(ApplicationIdProperty) as string; }
            set { SetValue(ApplicationIdProperty, value); }
        }

        /// <summary>
        /// Invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            MapControl = GetTemplateChild("Map") as Map;

            if (MapControl == null)
            {
                throw new PinnedMapException("Map control is not initialized");
            }

            if (string.IsNullOrEmpty(ApplicationId))
            {
                throw new PinnedMapException("Bing maps key not set");
            }

            MapControl.CredentialsProvider = new ApplicationIdCredentialsProvider(ApplicationId);
            MapControl.MapZoom += OnMapZoom;
            MapControl.MapPan += OnMapPan;
            SetCenterView();
        }

        void OnMapPan(object sender, MapDragEventArgs e)
        {
            if (Center == MapControl.Center)
                return;
            _skipCenterChangedNotification = true;
            Center = MapControl.Center;
        }

        private void OnMapZoom(object sender, MapZoomEventArgs e)
        {
            if (MapControl.ZoomLevel != Zoom)
                Zoom = MapControl.ZoomLevel;
        }

        private Map MapControl
        {
            get;
            set;
        }

        private void OnMapTap(object sender, System.Windows.Input.GestureEventArgs args)
        {
            var location = MapControl.ViewportPointToLocation(args.GetPosition(MapControl));
            OnNewLocationChosen(location);
            args.Handled = true;
        }

        private void SetCenterView()
        {
            if (Center == null || Center.IsUnknown)
            {
                return;
            }

            MapControl.SetView(Center, Zoom);
        }

        private void ShowPins()
        {
            MapControl.Children.Clear();

            foreach (var pin in Pins)
            {
                AddPin(pin);
            }
        }
      
        private void OnNewLocationChosen(GeoCoordinate newLocation)
        {
            if (NewLocationChosen != null)
            {
                NewLocationChosen(this, new NewLocationChosenEventArgs
                                            {
                                                Location = newLocation
                                            });
            }
        }

        private void OnPinsChanged()
        {
            if (MapControl == null || !Pins.Any())
            {
                return;
            }

            ShowPins();
        }

        private void PinsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null && MapControl.Children.Any())
            {
                foreach (Pin pin in e.OldItems)
                {
                    Remove(pin);
                }
            }

            if (e.NewItems != null )
            {
                foreach (Pin pin in e.NewItems)
                {
                    if (!pin.Location.IsUnknown)
                    {
                        AddPin(pin);
                    }
                }
            }
        }

        private void AddPin(Pin pin)
        {
            var pushpin = new Pushpin
            {
                Location = pin.Location,
                Content = pin.Title,
                Style = PushPinStyle
            };

            pushpin.Tap += (sender, args) =>
            {
                if (pin.TapAction != null)
                {
                    pin.TapAction.Invoke();
                }
            };

            pushpin.Tag = pin;
            MapControl.Children.Add(pushpin);
        }

        private void Remove(Pin pin)
        {
            var child = MapControl.Children.Where(ch =>
                                                      {
                                                          var pushPin = ch as Pushpin;
                                                          return pushPin != null && pushPin.Tag == pin;
                                                      }).SingleOrDefault();

            if (child != null)
            {
                MapControl.Children.Remove(child);
            }
        }

        private void OnCenterChanged()
        {
            if (_skipCenterChangedNotification)
            {
                _skipCenterChangedNotification = false;
                return;
            }
            if (MapControl == null)
            {
                return;
            }

            SetCenterView();
        }

        private static void OnPinsPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var pinnedMap = sender as PinnedMap;
            if (pinnedMap == null)
            {
                return;
            }

            if (args.OldValue != null)
            {
                ((ObservableCollection<Pin>)args.OldValue).CollectionChanged -= pinnedMap.PinsCollectionChanged;
            }

            if (args.NewValue != null)
            {
                ((ObservableCollection<Pin>)args.NewValue).CollectionChanged += pinnedMap.PinsCollectionChanged;
            }

            pinnedMap.OnPinsChanged();
        }

        private static void OnCenterPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            ((PinnedMap)sender).OnCenterChanged();
        }
    }
}
